///////////////////////////////////////////////////////////////////////////////
//
//  Copyright (C) 2007-2009 ILOG, S.A.
//  All Rights Reserved.
//  The following is ILOG Source Code.  No modifications may be made to the  
//  ILOG Source Code.  Any usage of the ILOG Source Code is subject to 
//  the terms and conditions of the ILOG End User License Agreement   
//  applicable to this ILOG software product.
//
///////////////////////////////////////////////////////////////////////////////

package ilog.gauges.circular
{
  import flash.events.Event;
  
  import ilog.gauges.gaugesClasses.GaugeElement;
  import ilog.gauges.gaugesClasses.LogScale;
  import ilog.gauges.gaugesClasses.circular.ICircularNumericScale;
  import ilog.utils.MathUtil;

  /**
   * The <code>CircularLogScale</code> class represents the logarithmic scale for circular 
   * gauges. This class can be used to populate the <i>scales</i> array of the gauge.
   * <p> 
   * If <code>startAngle</code> is equal to <code>endAngle</code>, the scale 
   * is a full circle and the last major tick is not generated since it has the 
   * same position as the first major tick.
   * </p> 
   *  
   * @see ilog.gauges.circular.CircularScale
   * @includeExample examples/CircularLogScaleExample.mxml -noswf 
   */
  public class CircularLogScale extends LogScale implements ICircularNumericScale
  {
    private var _startAngleRadians:Number;
    private var _endAngleRadians:Number;
    private var _totalAngleRadians:Number;
    private var _orientationNum:Number;

    /**
     * Constructor
     */
    public function CircularLogScale()
    {
      super();
      
      // Default values
      _startAngleRadians = MathUtil.toRadians(_startAngle);
      _endAngleRadians = MathUtil.toRadians(_endAngle);
      _orientationNum = -1;
      computeTotalAngleRadians();
    }

    /**
     *  @private
     */
    private var _startAngle:Number = 210;
    [Bindable("startAngleChanged")]    
    [Inspectable(category="Gauge")]        

    /**
     *  @copy ilog.gauges.gaugesClasses.circular.ICircularScale#startAngle
     */
    public function get startAngle():Number
    {
      return _startAngle;
    }

    /**
     *  @private
     */
    public function set startAngle(value:Number):void
    {
      _startAngleRadians = MathUtil.mod2PI(MathUtil.toRadians(value));
      computeTotalAngleRadians();
      _startAngle = value;
      invalidateTicks();
      dispatchEvent(new Event("startAngleChanged"));      
    }

    /**
     *  @private
     */
    private var _endAngle:Number = 330;
    [Bindable("endAngleChanged")]    
    [Inspectable(category="Gauge")]         

    /**
     *  @copy ilog.gauges.gaugesClasses.circular.ICircularScale#endAngle
     */
    public function get endAngle():Number
    {
      return _endAngle;
    }

    /**
     *  @private
     */
    public function set endAngle(value:Number):void
    {
      _endAngleRadians = MathUtil.mod2PI(MathUtil.toRadians(value));
      computeTotalAngleRadians();
      _endAngle = value;
      invalidateTicks();
      dispatchEvent(new Event("endAngleChanged"));      
    }


    /**
     *  @private
     */
    private var _orientation:String = "clockwise";

    [Bindable("orientationChanged")]
    [Inspectable(category="Gauge", enumeration="clockwise,cclockwise")]

    /**
     *  @copy ilog.gauges.gaugesClasses.circular.ICircularScale#orientation
     */
    public function get orientation():String
    {
      return _orientation;
    }

    /**
     *  @private
     */
    public function set orientation(value:String):void
    {
      _orientation = value;
      _orientationNum = value=="clockwise"?-1:1;
      computeTotalAngleRadians();
      invalidateTicks(); 
      dispatchEvent(new Event("orientationChanged"));      
    }

    /**
     *  @private
     */
    private function computeTotalAngleRadians():void
    {
      if (_startAngleRadians == _endAngleRadians)
        _totalAngleRadians = 2 * Math.PI;
      else
        _totalAngleRadians = computeAngle(_startAngleRadians, _endAngleRadians, orientation); 
    }

    /**
     *  @private
     */
    private function computeAngle(startAngle:Number, endAngle:Number, orientation:String):Number
    {
      var totalAngle:Number;
      
      if (endAngle == startAngle)
        totalAngle = 2 * Math.PI;
    
      if (orientation == "cclockwise")
      {
        if (endAngle < startAngle)
          totalAngle = 2 * Math.PI - (startAngle - endAngle);
        else
          totalAngle = endAngle - startAngle;
      }
      else
      {
        if (endAngle < startAngle)
          totalAngle = startAngle - endAngle;
        else
          totalAngle = 2 * Math.PI - (endAngle - startAngle);
      }
      return MathUtil.mod2PI(totalAngle);
    }

    /**
     *  @copy ilog.gauges.gaugesClasses.circular.ICircularScale#angleForValue()
     */
    public function angleForValue(value:Object):Number
    {
      var val:Number = Number(value);
      if (value == null || isNaN(val) || val <= 1)
        return _startAngleRadians;
      if (val >= maximum)
        return _endAngleRadians;  
      var v:Number = Math.log(Number(val)) / Math.LN10;
      var sv:Number = Math.log(computedMinimum) / Math.LN10;
      var ev:Number = Math.log(computedMaximum) / Math.LN10;
      
      var relativeValue:Number = _orientationNum * (v - sv);
      var angle:Number = MathUtil.mod2PI(_startAngleRadians + _totalAngleRadians * relativeValue/Math.abs(ev - sv)); 
      return angle;
    }

    /**
     *  @copy ilog.gauges.gaugesClasses.circular.ICircularScale#valueForAngle()
     */
    public function valueForAngle(angle:Number):Object
    {
      var sv:Number = Math.log(computedMinimum) / Math.LN10;
      var ev:Number = Math.log(computedMaximum) / Math.LN10;

      if (!angleInRange(angle)) {
        var min1:Number = MathUtil.mod2PI(_startAngleRadians - angle);
        var min2:Number = 2 * Math.PI - min1;
        var max1:Number = MathUtil.mod2PI(_endAngleRadians - angle);
        var max2:Number = 2 * Math.PI - max1;
        if (Math.min(min1, min2) < Math.min(max1, max2))
          return computedMinimum;
        else 
          return computedMaximum;
      }           
      var range:Number = Math.abs(sv - ev);
      var relativeAngle:Number = MathUtil.mod2PI(_orientationNum * (angle - _startAngleRadians));
      return Math.pow(10, sv + range * relativeAngle/_totalAngleRadians);
    }

    /**
     *  @copy ilog.gauges.gaugesClasses.circular.ICircularScale#angleInRange()
     */
    public function angleInRange(value:Number):Boolean
    {
      if (startAngle == endAngle)
        return true;
      value = MathUtil.mod2PI(value);
      if (_orientationNum == 1) {
        if (_startAngleRadians < _endAngleRadians)
          return value >= _startAngleRadians && value <= _endAngleRadians;
        else
          return ! (value > _endAngleRadians && value < _startAngleRadians);
      } else {
        if (_startAngleRadians < _endAngleRadians)
          return ! (value > _startAngleRadians && value < _endAngleRadians);
        else
          return value >= _endAngleRadians && value <= _startAngleRadians;
      }
    }
    
    /**
     *  @private
     */
    override protected function buildMajorTickItems():Array
    {
      var ret:Array = super.buildMajorTickItems();
      if (startAngle == endAngle)
        ret.pop();
      return ret;
    }

    /**
     * @copy ilog.gauges.gaugesClasses.IScale#invalidateElements()
     */
    override public function invalidateElements():void
    {
      super.invalidateElements();
      for each (var element:GaugeElement in elements) {
        element.invalidateProperties();
        element.invalidateSize();
      }
    }    
  }
}
